# -*- coding: utf-8 -*-
from __future__ import unicode_literals

import mimetypes
import re
import uuid

from django.db import models as m, transaction
from django.utils.translation import ugettext_lazy as _
import requests
from wechatpy.constants import WeChatErrorCode
from wechatpy.exceptions import WeChatClientException

from ..utils.model import enum2choices, model_fields
from . import appmethod, WeChatApp, WeChatModel


class MaterialManager(m.Manager):
    def create_material(self, **kwargs):
        """创建永久素材"""
        app = kwargs.pop("app", self.instance)
        type = kwargs.pop("type", None)

        if type is None:
            raise NotImplementedError()
        if type == Material.Type.NEWS:
            return self.create_news(app=app, **kwargs)
        else:
            media_id = kwargs["media_id"]
            if type == Material.Type.VIDEO and "url" not in kwargs:
                data = app.client.material.get(media_id)
                kwargs["url"] = data.get("down_url")

            kwargs = {
                key: kwargs[key]
                for key in model_fields(self.model)
                if key in kwargs
            }
            query = dict(app=app, type=type, media_id=media_id)
            record = dict(app=app, type=type, **kwargs)
            return self.update_or_create(defaults=record, **query)[0]

    def create_news(self, **kwargs):
        """创建永久图文素材"""
        from . import Article

        app = kwargs.pop("app", self.instance)

        # 插入media
        query = dict(app=app, media_id=kwargs["media_id"])
        record = dict(
            type=Material.Type.NEWS,
            update_time=kwargs["update_time"]
        )
        record.update(query)
        news, created = self.update_or_create(defaults=record, **query)
        if not created:
            # 移除所有article重新插入
            news.articles.all().delete()

        articles = Article.from_mp(kwargs, news)
        news.articles.bulk_create(articles)
        return news


class Material(WeChatModel):
    class Type(object):
        IMAGE = "image"
        VIDEO = "video"
        NEWS = "news"
        VOICE = "voice"

    app = m.ForeignKey(
        WeChatApp, related_name="materials", on_delete=m.CASCADE)

    type = m.CharField(
        _("type"), max_length=5, choices=(enum2choices(Type)))
    media_id = m.CharField(_("media_id"), max_length=64)
    alias = m.CharField(
        _("alias"), max_length=16, blank=True, null=True,
        help_text=_("素材别名,用于程序快速查询素材,单app下唯一"))

    name = m.CharField(_("name"), max_length=64, blank=True, null=True)
    url = m.CharField(_("url"), max_length=512, editable=False, null=True)
    update_time = m.IntegerField(
        _("update time"), editable=False, null=True)

    comment = m.TextField(_("comment"), blank=True)

    created_at = m.DateTimeField(_("created at"), auto_now_add=True)
    updated_at = m.DateTimeField(_("updated at"), auto_now=True)

    objects = MaterialManager()

    class Meta(object):
        verbose_name = _("material")
        verbose_name_plural = _("materials")

        unique_together = (("app", "media_id"), ("app", "alias"))
        ordering = ("app", "-update_time")

    @appmethod("sync_materials")
    def sync(cls, app, id=None, type=None):
        """同步所有永久素材"""
        if id:
            if type not in (cls.Type.NEWS, cls.Type.VIDEO):
                raise NotImplementedError()
            data = app.client.material.get_raw(id)
            return app.materials.create_material(
                type=type, media_id=id, **data)
        else:
            updated = []
            for type, _ in enum2choices(cls.Type):
                with transaction.atomic():
                    updates = cls.sync_type(app, type)
                    updated.extend(updates)
            return updated

    @appmethod("migrate_materials")
    def migrate(cls, app, src):
        migrated = []
        for type, _ in enum2choices(cls.Type):
            if type != cls.Type.NEWS:  # 不建议迁移图文 一天只能调10次
                with transaction.atomic():
                    migrates = app.migrate_type_materials(type, src)
                    migrated.extend(migrates)
        return migrated

    @appmethod("sync_type_materials")
    def sync_type(cls, app, type):
        """同步某种类型的永久素材"""
        updates = app.get_materials(type)
        # 删除被删除的 更新或新增获取的
        (app.materials.filter(type=type)
            .exclude(media_id__in=map(lambda o: o["media_id"], updates))
            .delete())
        return [app.materials.create_material(type=type, **item)
                for item in updates]

    @appmethod("migrate_type_materials")
    def migrate_type(cls, app, type, src):
        """从src公众号迁移某种类型的永久素材到app公众号"""
        materials = src.get_materials(type)
        if type == Material.Type.NEWS:
            return app.migrate_articles(app, src)
        else:
            migrated = []
            for material in materials:
                # TODO: 视频好像是down_url
                url = material.get("url") or material.get("down_url")
                resp = requests.get(url)

                # 上传素材
                record = app.upload_material(resp, permenant=True, save=True)
                migrated.append(record)
            return migrated

    @appmethod("get_materials")
    def get_all(cls, app, type):
        count = 20
        offset = 0
        rv = []
        while True:
            data = app.client.material.batchget(
                media_type=type,
                offset=offset,
                count=count
            )
            rv.extend(data["item"])
            if data["total_count"] <= offset + count:
                break
            offset += count
        return rv

    @appmethod("as_permenant_material")
    def as_permenant(cls, app, media_id, src=None, save=True):
        """将临时素材转换为永久素材"""
        src = src or app
        # 下载临时素材
        resp = src.client.media.download(media_id)

        # 上载素材
        return app.upload_material(resp, permenant=True, save=save)

    @appmethod("upload_material")
    def upload(cls, app, file, type=None, permenant=False, save=True):
        """
        上传素材
        :param type: image|voice|video|thumb
        :returns: media_id,如果保存返回Material实例
        """
        if isinstance(file, requests.Response):
            fileinfo = cls.get_response_file_info(file, type)
            filename = fileinfo.get("filename")
            file = (filename, file.content)
            if not type:
                type = fileinfo.get("type")

        assert type is not None

        if type == Material.Type.NEWS:
            permenant = True
            data = app.client.material.add_articles(file)
        elif permenant:
            data = app.client.material.add(type, file)
        else:
            data = app.client.media.upload(type, file)
        media_id = data["media_id"]

        if permenant and save:
            if type in (Material.Type.NEWS, Material.Type.VIDEO):
                return app.sync_materials(media_id, type=type)
            else:
                return app.materials.create_material(
                    type=type, media_id=media_id, url=data.get("url"))
        return media_id

    @appmethod("download_material")
    def download(cls, app, media_id, permenant=True):
        if permenant:
            data = app.client.material.get_raw(media_id)
            if isinstance(data, dict) and "down_url" in data:
                # 视频
                return requests.get(data["down_url"])
            # elif isinstance(data, list):
            #     # 图文
            #     raise NotImplementedError("Cannot download an article")
            # 其他素材直接响应内容了
            return data
        else:
            return app.client.media.download(media_id)

    @property
    def articles_json(self):
        return list(map(lambda o: dict(
            title=o.title,
            description=o.digest,
            image=o.thumb_url,
            url=o.url
        ), self.articles.all()))

    def save(self, *args, **kwargs):
        self.alias = self.alias or None
        return super(Material, self).save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        # 先远程素材删除
        try:
            self.app.client.material.delete(self.media_id)
        except WeChatClientException as e:
            if e.errcode != WeChatErrorCode.INVALID_MEDIA_ID:
                raise
        rv = super(Material, self).delete(*args, **kwargs)
        return rv

    @staticmethod
    def get_response_file_info(resp, type=None):
        try:
            content_type = resp.headers["Content-Type"]
        except:
            content_type = ""
        if not type:
            # 已知audio会变成text/html
            if content_type == "text/html":
                content_type = "audio/mp3"

            if content_type.startswith("image"):
                type = Material.Type.IMAGE
            elif content_type.startswith("video"):
                type = Material.Type.VIDEO
            elif content_type.startswith("audio"):
                type = Material.Type.VOICE

        ext = mimetypes.guess_extension(content_type)
        if ext == ".jpe":
            ext = ".jpg"
        # 没有ext根据type固定ext
        if not ext:
            if type == Material.Type.IMAGE:
                ext = ".jpg"
            elif type == Material.Type.VIDEO:
                ext = ".mp4"
            elif type == Material.Type.VOICE:
                ext = ".mp3"

        # 找文件名
        try:
            disposition = resp.headers["Content-Disposition"]
            filename = re.findall(r'filename="(.+?)"', disposition)[0]
            filename_tuple = filename.split(".")
            if len(filename_tuple) == 1:
                # 无扩展名
                filename = filename + ext
        except:
            filename = str(uuid.uuid4()) + ext

        return dict(
            filename=filename,
            ext=ext,
            type=type,
            content_type=content_type
        )

    def __str__(self):
        media = "{type}:{media_id}".format(type=self.type,
                                           media_id=self.media_id)
        return "{0} ({1})".format(self.comment, media)\
            if self.comment else media
